import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;

public class Game2048 extends JFrame {
    private static final int SIZE = 4;
    private Tile[][] board;
    private Random random;

    public Game2048() {
        setTitle("2048 Game");
        setSize(400, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        board = new Tile[SIZE][SIZE];
        random = new Random();

        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                board[i][j] = new Tile();
            }
        }

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        moveUp();
                        break;
                    case KeyEvent.VK_DOWN:
                        moveDown();
                        break;
                    case KeyEvent.VK_LEFT:
                        moveLeft();
                        break;
                    case KeyEvent.VK_RIGHT:
                        moveRight();
                        break;
                }
                addRandomTile();
                repaint();
            }
        });

        addRandomTile();
        addRandomTile();
    }

    private void moveUp() {
        for (int j = 0; j < SIZE; j++) {
            for (int i = 0; i < SIZE - 1; i++) {
                for (int k = i + 1; k < SIZE; k++) {
                    if (board[k][j].getValue() > 0) {
                        if (board[i][j].getValue() == 0) {
                            board[i][j].setValue(board[k][j].getValue());
                            board[k][j].reset();
                        } else if (board[i][j].getValue() == board[k][j].getValue() &&!board[i][j].isMerged() &&!board[k][j].isMerged()) {
                            board[i][j].setValue(board[i][j].getValue() * 2);
                            board[i][j].setMerged(true);
                            board[k][j].reset();
                        }
                        break;
                    }
                }
            }
            for (int i = 0; i < SIZE; i++) {
                board[i][j].setMerged(false);
            }
        }
    }

    private void moveDown() {
        for (int j = 0; j < SIZE; j++) {
            for (int i = SIZE - 1; i > 0; i--) {
                for (int k = i - 1; k >= 0; k--) {
                    if (board[k][j].getValue() > 0) {
                        if (board[i][j].getValue() == 0) {
                            board[i][j].setValue(board[k][j].getValue());
                            board[k][j].reset();
                        } else if (board[i][j].getValue() == board[k][j].getValue() &&!board[i][j].isMerged() &&!board[k][j].isMerged()) {
                            board[i][j].setValue(board[i][j].getValue() * 2);
                            board[i][j].setMerged(true);
                            board[k][j].reset();
                        }
                        break;
                    }
                }
            }
            for (int i = 0; i < SIZE; i++) {
                board[i][j].setMerged(false);
            }
        }
    }

    private void moveLeft() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE - 1; j++) {
                for (int k = j + 1; k < SIZE; k++) {
                    if (board[i][k].getValue() > 0) {
                        if (board[i][j].getValue() == 0) {
                            board[i][j].setValue(board[i][k].getValue());
                            board[i][k].reset();
                        } else if (board[i][j].getValue() == board[i][k].getValue() &&!board[i][j].isMerged() &&!board[i][k].isMerged()) {
                            board[i][j].setValue(board[i][j].getValue() * 2);
                            board[i][j].setMerged(true);
                            board[i][k].reset();
                        }
                        break;
                    }
                }
            }
            for (int j = 0; j < SIZE; j++) {
                board[i][j].setMerged(false);
            }
        }
    }

    private void moveRight() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = SIZE - 1; j > 0; j--) {
                for (int k = j - 1; k >= 0; k--) {
                    if (board[i][k].getValue() > 0) {
                        if (board[i][j].getValue() == 0) {
                            board[i][j].setValue(board[i][k].getValue());
                            board[i][k].reset();
                        } else if (board[i][j].getValue() == board[i][k].getValue() &&!board[i][j].isMerged() &&!board[i][k].isMerged()) {
                            board[i][j].setValue(board[i][j].getValue() * 2);
                            board[i][j].setMerged(true);
                            board[i][k].reset();
                        }
                        break;
                    }
                }
            }
            for (int j = 0; j < SIZE; j++) {
                board[i][j].setMerged(false);
            }
        }
    }

    private void addRandomTile() {
        int count = 0;
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                if (board[i][j].getValue() == 0) {
                    count++;
                }
            }
        }
        if (count > 0) {
            int randomIndex = random.nextInt(count);
            int index = 0;
            for (int i = 0; i < SIZE; i++) {
                for (int j = 0; j < SIZE; j++) {
                    if (board[i][j].getValue() == 0) {
                        if (index == randomIndex) {
                            board[i][j].setValue((random.nextInt(10) == 0)? 4 : 2);
                            return;
                        }
                        index++;
                    }
                }
            }
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                int x = j * 100;
                int y = i * 100;
                drawTile(g2d, board[i][j], x, y);
            }
        }
    }

    private void drawTile(Graphics2D g2d, Tile tile, int x, int y) {
        g2d.setColor(new Color(204, 192, 179));
        g2d.fillRect(x, y, 100, 100);
        if (tile.getValue() > 0) {
            g2d.setColor(getColor(tile.getValue()));
            g2d.fillRoundRect(x + 10, y + 10, 80, 80, 15, 15);
            g2d.setColor(Color.WHITE);
            g2d.setFont(new Font("Arial", Font.BOLD, 30));
            String value = String.valueOf(tile.getValue());
            FontMetrics fm = g2d.getFontMetrics();
            int textX = x + (100 - fm.stringWidth(value)) / 2;
            int textY = y + (100 - fm.getHeight()) / 2 + fm.getAscent();
            g2d.drawString(value, textX, textY);
        }
    }

    private Color getColor(int value) {
        switch (value) {
            case 2:
                return new Color(238, 228, 218);
            case 4:
                return new Color(237, 224, 200);
            case 8:
                return new Color(242, 177, 121);
            case 16:
                return new Color(245, 149, 99);
            case 32:
                return new Color(246, 124, 95);
            case 64:
                return new Color(246, 94, 59);
            case 128:
                return new Color(237, 207, 114);
            case 256:
                return new Color(237, 204, 97);
            case 512:
                return new Color(237, 200, 80);
            case 1024:
                return new Color(237, 197, 63);
            case 2048:
                return new Color(237, 194, 46);
            default:
                return new Color(0, 0, 0);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            Game2048 game = new Game2048();
            game.setVisible(true);
        });
    }
}